package com.elinkway.infinitemovies.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.Scroller;

import com.elinkway.infinitemovies.application.MoviesApplication;
import com.le123.ysdq.R;

public class OverScrollLayout extends FrameLayout {

    private static final int MAX_DY_DP = 95;
    private static final int HOVER_TAP_SLOP = 20;
    private static final int DIRECTION_LEFT = 0x0001;
    private static final int DIRECTION_TOP = 0x0010;
    private static final int DIRECTION_RIGHT = 0x0100;
    private static final int DIRECTION_BOTTOM = 0x1000;
    private static final int DIRECTION_DEFAULT = DIRECTION_TOP;
    private static final float DEFAULT_FACTOR = 1;

    //over scroll direction
    private int mTargetDirection;
    //over scroll direction factor
    private float mFactor = DEFAULT_FACTOR;
    private Scroller mScroller;
    private MotionEvent mLastMotionEvent, mLastInterceptMotionEvent;
    private int mLastDirection;
    private OnDampingCallback mOnDampingCallback;
    private boolean mIsOverScrollEnable = true;

    private int mLeftLayout = -1;
    private int mRightLayout = -1;
    private int mTopLayout = -1;
    private int mBottomLayout = -1;

    private boolean mLeftLayoutHasAdd = false;
    private boolean mRightLayoutHasAdd = false;
    private boolean mTopLayoutHasAdd = false;
    private boolean mBottomLayoutHasAdd = false;


    public OverScrollLayout(Context context) {
        this(context, null);
    }

    public OverScrollLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public OverScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray tArray = context.obtainStyledAttributes(attrs,
                R.styleable.OverScrollLayout, defStyleAttr, 0);
        int count = tArray.getIndexCount();
        for (int i = 0; i < count; i++) {
            int index = tArray.getIndex(i);
            switch (index) {
                case R.styleable.OverScrollLayout_dampingDirection:
                    mTargetDirection |= tArray.getInt(index, DIRECTION_DEFAULT);
                    break;
                case R.styleable.OverScrollLayout_dampingFactor:
                    mFactor = tArray.getFloat(index, DEFAULT_FACTOR);
                    break;
                case R.styleable.OverScrollLayout_dampingTopLayout:
                    mTopLayout = tArray.getResourceId(R.styleable.OverScrollLayout_dampingTopLayout, -1);
                    break;
                case R.styleable.OverScrollLayout_dampingBottomLayout:
                    mBottomLayout = tArray.getResourceId(R.styleable.OverScrollLayout_dampingBottomLayout, -1);
                    break;
                case R.styleable.OverScrollLayout_dampingLeftLayout:
                    mLeftLayout = tArray.getResourceId(R.styleable.OverScrollLayout_dampingLeftLayout, -1);
                    break;
                case R.styleable.OverScrollLayout_dampingRightLayout:
                    mRightLayout = tArray.getResourceId(R.styleable.OverScrollLayout_dampingRightLayout, -1);
                    break;
            }
        }
        tArray.recycle();
        createTipsView(mTopLayout, DIRECTION_TOP);
        createTipsView(mBottomLayout, DIRECTION_BOTTOM);
        createTipsView(mLeftLayout, DIRECTION_LEFT);
        createTipsView(mRightLayout, DIRECTION_RIGHT);
        mScroller = new Scroller(context);
    }

    private void createTipsView(int resId, final int direction) {
        if (resId == -1) return;
        FrameLayout.LayoutParams params = null;
        if (direction == DIRECTION_TOP) {
            if (mTopLayoutHasAdd) return;
            params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            params.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
            mTopLayoutHasAdd = true;
        } else if (direction == DIRECTION_BOTTOM) {
            if (mBottomLayoutHasAdd) return;
            params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
            mBottomLayoutHasAdd = true;
        } else if (direction == DIRECTION_LEFT) {
            if (mLeftLayoutHasAdd) return;
            params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
            params.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
            mLeftLayoutHasAdd = true;
        } else {
            if (mRightLayoutHasAdd) return;
            params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
            params.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
            mRightLayoutHasAdd = true;
        }
        final View view = inflate(getContext(), resId, null);
        this.addView(view, params);
        view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (view.getHeight() != 0 && view.getWidth() != 0) {
                    if (direction == DIRECTION_TOP) {
                        view.setY(view.getY() - view.getHeight());
                    } else if (direction == DIRECTION_BOTTOM) {
                        view.setY(view.getY() + view.getHeight());
                    } else if (direction == DIRECTION_LEFT) {
                        view.setX(view.getX() - view.getWidth());
                    } else {
                        view.setX(view.getX() + view.getWidth());
                    }
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    } else {
                        view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }
                }
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept;
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            intercept = false;
        } else {
            if (null == mOnDampingCallback) {
                mOnDampingCallback = try2GetOnDampingCallback();
            }
            intercept = isMoving(ev, mLastInterceptMotionEvent) && mOnDampingCallback.needDamping(ev, mLastInterceptMotionEvent);
        }
        mLastInterceptMotionEvent = MotionEvent.obtain(ev);
        return intercept;
    }

    private boolean isMoving(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
        int dx = (int) (newMotionEvent.getRawX() - oldMotionEvent.getRawX());
        int dy = (int) (newMotionEvent.getRawY() - oldMotionEvent.getRawY());
        return Math.abs(dx) > HOVER_TAP_SLOP || Math.abs(dy) > HOVER_TAP_SLOP;
    }

    private OnDampingCallback try2GetOnDampingCallback() {
        //no tips view
        View child = getChildAt(0);
        //has tips view
        if (getChildCount() > 1) {
            child = getChildAt(1);
        }
        if (child instanceof ViewPager) {
            return new ViewPagerDampingCallback((ViewPager) child);
        } else if (child instanceof ScrollView) {
            return new ScrollViewDampingCallback((ScrollView) child);
        } else if (child instanceof HorizontalScrollView) {
            return new HorizontalScrollViewDampingCallback((HorizontalScrollView) child);
        } else if (child instanceof RecyclerView) {
            return new RecyclerViewDampingCallback((RecyclerView) child);
        }
        return new SimpleDampingCallback();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        int currentDirection = -1;
        if (mIsOverScrollEnable) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mLastMotionEvent = MotionEvent.obtain(event);
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (null == mLastMotionEvent) {
                        //MotionEvent consume by sub view, will not call ACTION_DOWN
                        mLastMotionEvent = mLastInterceptMotionEvent;
                    }
                    int dx = (int) (event.getRawX() - mLastMotionEvent.getRawX());
                    int dy = (int) (event.getRawY() - mLastMotionEvent.getRawY());
                    //horizontal diff > vertical
                    if (Math.abs(dx) > Math.abs(dy)) {
                        //Left
                        if (dx > 0 && (mTargetDirection & DIRECTION_LEFT) == DIRECTION_LEFT) {
                            currentDirection = DIRECTION_LEFT;
                        }
                        //Right
                        else if (dx < 0 && (mTargetDirection & DIRECTION_RIGHT) == DIRECTION_RIGHT) {
                            currentDirection = DIRECTION_RIGHT;
                        }
                        //is over scrolling in one direction, another will not
                        boolean isHorizontal = mLastDirection == DIRECTION_LEFT || mLastDirection == DIRECTION_RIGHT;
                        boolean isDirectionEnable = currentDirection != -1 && (mTargetDirection & currentDirection) == currentDirection;
                        if (mLastDirection == 0 || isHorizontal && isDirectionEnable) {
                            mLastDirection = currentDirection;
                            smoothScrollBy(-(int) (dx * mFactor), 0);
                        }
                    } else {
                        //top
                        if (dy > 0 && (mTargetDirection & DIRECTION_TOP) == DIRECTION_TOP) {
                            currentDirection = DIRECTION_TOP;
                        }
                        //bottom
                        else if (dy < 0 && (mTargetDirection & DIRECTION_BOTTOM) == DIRECTION_BOTTOM) {
                            currentDirection = DIRECTION_BOTTOM;
                        }
                        //is over scrolling in one direction, another will not
                        boolean isVertical = mLastDirection == DIRECTION_TOP || mLastDirection == DIRECTION_BOTTOM;
                        boolean isDirectionEnable = currentDirection != -1 && (mTargetDirection & currentDirection) == currentDirection;
                        if (mLastDirection == 0 || isVertical && isDirectionEnable) {
                            mLastDirection = currentDirection;
                            smoothScrollBy(0, -(int) (dy * mFactor));
                        }
                    }
                    mLastMotionEvent = MotionEvent.obtain(event);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    mLastDirection = 0;
                    mLastMotionEvent = null;
                    smoothScrollTo(0, 0);
                    break;
            }
            return true;
        }
        return false;
    }

    private void smoothScrollTo(int fx, int fy) {
        int dx = fx - mScroller.getFinalX();
        int dy = fy - mScroller.getFinalY();
        smoothScrollBy(dx, dy);
    }

    private void smoothScrollBy(int dx, int dy) {
        if (mScroller.getFinalY() > dp2px(MAX_DY_DP) && dy > 0 && mBottomLayoutHasAdd) {
            return;
        } else {
            mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
            invalidate();
        }
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
        super.computeScroll();
    }

    public static boolean isMoving2Left(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
        int dx = (int) (newMotionEvent.getRawX() - oldMotionEvent.getRawX());
        int dy = (int) (newMotionEvent.getRawY() - oldMotionEvent.getRawY());
        return dx > 0 && Math.abs(dx) > Math.abs(dy);
    }

    public static boolean isMoving2Top(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
        int dx = (int) (newMotionEvent.getRawX() - oldMotionEvent.getRawX());
        int dy = (int) (newMotionEvent.getRawY() - oldMotionEvent.getRawY());
        return dy > 0 && Math.abs(dx) < Math.abs(dy);
    }

    public static boolean isMoving2Right(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
        int dx = (int) (newMotionEvent.getRawX() - oldMotionEvent.getRawX());
        int dy = (int) (newMotionEvent.getRawY() - oldMotionEvent.getRawY());
        return dx < 0 && Math.abs(dx) > Math.abs(dy);
    }

    public static boolean isMoving2Bottom(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
        int dx = (int) (newMotionEvent.getRawX() - oldMotionEvent.getRawX());
        int dy = (int) (newMotionEvent.getRawY() - oldMotionEvent.getRawY());
        return dy < 0 && Math.abs(dx) < Math.abs(dy);
    }

    public void setOnDampingCallback(OnDampingCallback callback) {
        mOnDampingCallback = callback;
    }

    public interface OnDampingCallback {
        boolean needDamping(MotionEvent newMotionEvent, MotionEvent oldMotionEvent);
    }

    public static class SimpleDampingCallback implements OnDampingCallback {
        @Override
        public boolean needDamping(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
            return true;
        }
    }

    public static class ViewPagerDampingCallback implements OnDampingCallback {
        private ViewPager mViewPager;

        public ViewPagerDampingCallback(ViewPager viewPager) {
            mViewPager = viewPager;
        }

        @Override
        public boolean needDamping(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
            if (null == newMotionEvent || null == oldMotionEvent) {
                return false;
            }
            //Weather Left
            boolean isLeftDamping = isMoving2Left(newMotionEvent, oldMotionEvent) && mViewPager.getCurrentItem() == 0;
            //Weather Right
            boolean isRightDamping = isMoving2Right(newMotionEvent, oldMotionEvent) && mViewPager.getCurrentItem() == mViewPager.getAdapter().getCount() - 1;
            return isLeftDamping || isRightDamping;
        }
    }

    public static class ScrollViewDampingCallback implements OnDampingCallback {
        private ScrollView mScrollView;

        public ScrollViewDampingCallback(ScrollView scrollView) {
            mScrollView = scrollView;
        }

        @Override
        public boolean needDamping(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
            if (null == newMotionEvent || null == oldMotionEvent) {
                return false;
            }
            boolean isTopDamping = isMoving2Top(newMotionEvent, oldMotionEvent) && mScrollView.getScrollY() == 0;
            View childView = mScrollView.getChildAt(0);
            boolean isBottomDamping = isMoving2Bottom(newMotionEvent, oldMotionEvent)
                    && childView != null && childView.getMeasuredHeight() <= mScrollView.getScrollY() + mScrollView.getHeight();
            return isTopDamping || isBottomDamping;
        }
    }

    public static class HorizontalScrollViewDampingCallback implements OnDampingCallback {
        private HorizontalScrollView mScrollView;

        public HorizontalScrollViewDampingCallback(HorizontalScrollView scrollView) {
            mScrollView = scrollView;
        }

        @Override
        public boolean needDamping(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
            if (null == newMotionEvent || null == oldMotionEvent) {
                return false;
            }
            boolean isLeftDamping = isMoving2Left(newMotionEvent, oldMotionEvent) && mScrollView.getScrollX() == 0;
            View childView = mScrollView.getChildAt(0);
            boolean isRightDamping = isMoving2Right(newMotionEvent, oldMotionEvent)
                    && childView != null && childView.getMeasuredWidth() <= mScrollView.getScrollX() + mScrollView.getWidth();
            return isLeftDamping || isRightDamping;
        }
    }

    public static class RecyclerViewDampingCallback implements OnDampingCallback {
        private RecyclerView mRecyclerView;

        public RecyclerViewDampingCallback(RecyclerView recyclerView) {
            mRecyclerView = recyclerView;
        }

        @Override
        public boolean needDamping(MotionEvent newMotionEvent, MotionEvent oldMotionEvent) {
            if (null == newMotionEvent || null == oldMotionEvent) {
                return false;
            }
            boolean isTopDamping = isMoving2Top(newMotionEvent, oldMotionEvent) && !(ViewCompat.canScrollVertically(mRecyclerView, -1));
            boolean isBottomDamping = isMoving2Bottom(newMotionEvent, oldMotionEvent) && !ViewCompat.canScrollVertically(mRecyclerView, 1);
            return isTopDamping || isBottomDamping;
        }
    }

    public void setOverScrollEnable(boolean overScrollEnable) {
        mIsOverScrollEnable = overScrollEnable;
    }

    public static int dp2px(float pxVal) {
        final float scale = MoviesApplication.getInstance().getResources().getDisplayMetrics().density;
        return (int)(pxVal * scale);

    }
}